[アップデート] AWS SAM CLI の 1.110.0 でも .NET 8 がサポートされたので、マネージドランタイム上での Native AOT を使ってみた
いわさです。
先日 AWS Lambda のマネージドランタイムでついに .NET 8 が使えるようになったことを紹介しました。
Lambda 側のアップデートにあわせて、今朝の AWS SAM CLI の最新リリース 1.110.0 でも .NET 8 がサポートされました。
feat: add dotnet8 support (#6429) (#6723)
本記事では AWS SAM CLI から .NET 8 関数の作成・ビルド・デプロイを行ってみました。
また、マネージドランタイムでも Native AOT が使えることがわかったので Native AOT 有効した時としていない時でコールドスタート時間を計測してみました。
AWS SAM CLI 1.110.0 以上へアップデートする
前提として AWS SAM CLI のバージョンを本日時点の最新版 1.110.0 へアップデートしておきます。
% sam --version SAM CLI, version 1.110.0
Native AOT のテンプレートも用意されている
あとは普段の SAM CLI の使い方のとおりでsam init
で関数をテンプレートから作成します。
オプション指定なしでsam init
してみるとランタイムオプションにdotnet8
が追加されていることが確認出来ます。
% sam init You can preselect a particular runtime or package type when using the `sam init` experience. Call `sam init --help` to learn more. Which template source would you like to use? 1 - AWS Quick Start Templates 2 - Custom Template Location Choice: 1 Choose an AWS Quick Start application template 1 - Hello World Example 2 - Data processing 3 - Hello World Example with Powertools for AWS Lambda 4 - Multi-step workflow 5 - Scheduled task 6 - Standalone function 7 - Serverless API 8 - Infrastructure event management 9 - Lambda Response Streaming 10 - Serverless Connector Hello World Example 11 - Multi-step workflow with Connectors 12 - GraphQLApi Hello World Example 13 - Full Stack 14 - Lambda EFS example 15 - Hello World Example With Powertools for AWS Lambda 16 - DynamoDB Example 17 - Machine Learning Template: 1 Use the most popular runtime and package type? (Python and zip) [y/N]: Which runtime would you like to use? 1 - aot.dotnet7 (provided.al2) 2 - dotnet8 3 - dotnet6 4 - go1.x 5 - go (provided.al2) 6 - go (provided.al2023) 7 - graalvm.java11 (provided.al2) 8 - graalvm.java17 (provided.al2) 9 - java21 10 - java17 11 - java11 12 - java8.al2 13 - nodejs20.x 14 - nodejs18.x 15 - nodejs16.x 16 - python3.9 17 - python3.8 18 - python3.12 19 - python3.11 20 - python3.10 21 - ruby3.2 22 - rust (provided.al2) 23 - rust (provided.al2023) Runtime:
ランタイムオプションを指定した上でsam init
したところテンプレートは次のものが使えました。
ここで気がついたのですが、テンプレート選択後にさらに Native AOT を使うかどうか選択が出来ますね。
Native AOT というのは .NET 7 から使えるようになった機能で、JIT ではなく事前にネイティブコンパイルしておくことで実行時の JIT によるオーバーヘッドを軽減出来る仕組みです。
Lambda の場合だと特にコールドスタート時間の削減を期待することが出来ます。
全てのクイックスタートテンプレートが対応しているかわからないですが、Hello World Example
では Native AOT を使う場合と使わない場合を選択することが出来ました。
上述の記事を見ていただくとわかるのですが、Native AOT の初期設定はひと手間必要なので、これはありがたいですね。
% sam init --runtime dotnet8 Which template source would you like to use? 1 - AWS Quick Start Templates 2 - Custom Template Location Choice: 1 Choose an AWS Quick Start application template 1 - Hello World Example 2 - Data processing 3 - Hello World Example with Powertools for AWS Lambda 4 - Multi-step workflow 5 - Scheduled task 6 - Standalone function 7 - Serverless API Template: 1 Based on your selections, the only Package type available is Zip. We will proceed to selecting the Package type as Zip. Based on your selections, the only dependency manager available is cli-package. We will proceed copying the template using cli-package. Select your starter template 1 - Hello World Example 2 - Hello World Example using native AOT Template: 2 :
sam init
で作成された Native AOT テンプレートを確認してみると次のような設定値となっていました。
どうやらマネージドランタイム .NET 8 で Native AOT 使えるっぽいですね。てっきり .NET 8 になってもカスタムランタイム必要かも?と思っていたのでこれは期待出来る。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > Sample SAM Template for hoge0223dotnet8 # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst Globals: Function: Timeout: 10 MemorySize: 512 Resources: HelloWorldAotFunction: Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction Properties: CodeUri: ./src/HelloWorldAot/ Handler: bootstrap Runtime: dotnet8 Architectures: - x86_64 MemorySize: 512 Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object Variables: PARAM1: VALUE Events: HelloWorldAot: Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api Properties: Path: /hello Method: get :
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <AWSProjectType>Lambda</AWSProjectType> <AssemblyName>bootstrap</AssemblyName> <!-- This property makes the build directory similar to a publish directory and helps the AWS .NET Lambda Mock Test Tool find project dependencies. --> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <!-- Generate native aot images during publishing to improve cold start time. --> <PublishAot>true</PublishAot> <!-- StripSymbols tells the compiler to strip debugging symbols from the final executable if we're on Linux and put them into their own file. This will greatly reduce the final executable's size.--> <StripSymbols>true</StripSymbols> </PropertyGroup> <ItemGroup> <PackageReference Include="Amazon.Lambda.Core" Version="2.2.0" /> <PackageReference Include="Amazon.Lambda.RuntimeSupport" Version="1.10.0" /> <PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.4.0" /> <PackageReference Include="Amazon.Lambda.APIGatewayEvents" Version="2.7.0" /> </ItemGroup> </Project>
マネージドランタイムとして Native AOT もデプロイ可能
あとはこのままsam build
、sam deploy
してみましょう。
今回はHello World Example
とHello World Example using native AOT
をどちらもデプロイしてみました。
マネジメントコンソールで確認してみると次のような関数構成となっていました。
やはりマネージドランタイムが使われていることが確認出来まして、Native AOT はパッケージサイズが非 Native AOT よりも大きくなっており、ハンドラが bootstrap となっています。パッケージサイズが大きくなるのは Native AOT のデメリットの一つなんですよね。
非 Native AOT
Native AOT
コールドスタート時の処理時間を計測してみる
さて、Native AOT によってコールドスタート発生時の初期化時間を軽減することが期待されます。
今回こちらも試してみましたので紹介します。
まず、ウォームスタート時の時間は次のようになっていました。
このテンプレートは API Gateway から Lambda 関数が呼び出される形となっているので cURL でエンドポイントにリクエストを送信して時間を計測してみました。
# Native AOT % curl -w "code: %{http_code}, time_total: %{time_total}\n" -o /dev/null -s https://qbbby9d9ja.execute-api.ap-northeast-1.amazonaws.com/Prod/hello code: 200, time_total: 0.416626 % curl -w "code: %{http_code}, time_total: %{time_total}\n" -o /dev/null -s https://qbbby9d9ja.execute-api.ap-northeast-1.amazonaws.com/Prod/hello code: 200, time_total: 0.455187 % curl -w "code: %{http_code}, time_total: %{time_total}\n" -o /dev/null -s https://qbbby9d9ja.execute-api.ap-northeast-1.amazonaws.com/Prod/hello code: 200, time_total: 0.460408 # 非 Native AOT % curl -w "code: %{http_code}, time_total: %{time_total}\n" -o /dev/null -s https://c5q0rwiofj.execute-api.ap-northeast-1.amazonaws.com/Prod/hello code: 200, time_total: 0.397671 % curl -w "code: %{http_code}, time_total: %{time_total}\n" -o /dev/null -s https://c5q0rwiofj.execute-api.ap-northeast-1.amazonaws.com/Prod/hello code: 200, time_total: 0.440831 % curl -w "code: %{http_code}, time_total: %{time_total}\n" -o /dev/null -s https://c5q0rwiofj.execute-api.ap-northeast-1.amazonaws.com/Prod/hello code: 200, time_total: 0.357376
どちらもあまり変わらないですね。
続いてコールドスタート速度を計測してみます。
コールドスタートを発生させるために、関数呼び出し前に毎回関数の構成変更(今回はタイムアウト値)を行っています。
非 Native AOT の場合のコールドスタート発生時の時間は次のようになっていました。
% curl -w "code: %{http_code}, time_total: %{time_total}\n" -o /dev/null -s https://c5q0rwiofj.execute-api.ap-northeast-1.amazonaws.com/Prod/hello code: 200, time_total: 2.123933 % curl -w "code: %{http_code}, time_total: %{time_total}\n" -o /dev/null -s https://c5q0rwiofj.execute-api.ap-northeast-1.amazonaws.com/Prod/hello code: 200, time_total: 1.634230 % curl -w "code: %{http_code}, time_total: %{time_total}\n" -o /dev/null -s https://c5q0rwiofj.execute-api.ap-northeast-1.amazonaws.com/Prod/hello code: 200, time_total: 1.407865 % curl -w "code: %{http_code}, time_total: %{time_total}\n" -o /dev/null -s https://c5q0rwiofj.execute-api.ap-northeast-1.amazonaws.com/Prod/hello code: 200, time_total: 1.381490
Native AOT の場合のコールドスタート時間は次のようになっていました。
% curl -w "code: %{http_code}, time_total: %{time_total}\n" -o /dev/null -s https://qbbby9d9ja.execute-api.ap-northeast-1.amazonaws.com/Prod/hello code: 200, time_total: 0.968165 % curl -w "code: %{http_code}, time_total: %{time_total}\n" -o /dev/null -s https://qbbby9d9ja.execute-api.ap-northeast-1.amazonaws.com/Prod/hello code: 200, time_total: 0.968708 % curl -w "code: %{http_code}, time_total: %{time_total}\n" -o /dev/null -s https://qbbby9d9ja.execute-api.ap-northeast-1.amazonaws.com/Prod/hello code: 200, time_total: 0.807188 % curl -w "code: %{http_code}, time_total: %{time_total}\n" -o /dev/null -s https://qbbby9d9ja.execute-api.ap-northeast-1.amazonaws.com/Prod/hello code: 200, time_total: 1.000450
あー、これは結構速くなっている感じがしますね!
HelloWorld でこれなので、もう少し大きいモジュールだともっと改善が期待出来るのではないでしょうか。
さいごに
本日は AWS SAM CLI の 1.110.0 でも .NET 8 がサポートされたので、マネージドランタイム上での Native AOT を使ってみました。
「.NET も SnapStart はよ」といつも思ってはいるのですが、まだ使えない状況です。
そんな中で Native AOT は .NET Lambda のパフォーマンス改善のオプションとして有効です。
以前までは LTS ではない .NET 7 でカスタムランタイムでの実行だったので Native AOT の採用はもう少し先かなと思っていたのですが、今回マネージドランタイムで気楽に導入出来るようになったので、少数派だとは思いますが .NET Lambda を使われている方は是非 Native AOT の採用も検討してみては如何でしょうか。